package com.andaily.springoauth.service.impl;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.andaily.springoauth.infrastructure.httpclient.HttpClientExecutor;
import com.andaily.springoauth.infrastructure.httpclient.HttpClientPostExecutor;
import com.andaily.springoauth.service.OauthService;
import com.andaily.springoauth.service.dto.AccessTokenDto;
import com.andaily.springoauth.service.dto.AuthAccessTokenDto;
import com.andaily.springoauth.service.dto.AuthCallbackDto;
import com.andaily.springoauth.service.dto.AuthorizationCodeDto;
import com.andaily.springoauth.service.dto.RefreshAccessTokenDto;
import com.andaily.springoauth.service.dto.UserDto;
import com.andaily.springoauth.service.password.PasswordParams;
import com.andaily.springoauth.web.WebUtils;

/**
 * 业务处理层
 */
@Service("oauthService")
public class OauthServiceImpl implements OauthService {

	private static final Logger	LOG	= LoggerFactory.getLogger(OauthServiceImpl.class);

	@Value("#{properties['access-token-uri']}")
	private String				accessTokenUri;

	@Value("#{properties['unityUserInfoUri']}")
	private String				unityUserInfoUri;

	@Value("#{properties['user-authorization-uri']}")
	private String				userAuthorizationUri;

	@Value("#{properties['mobileUserInfoUri']}")
	private String				mobileUserInfoUri;

	private HttpServletRequest	request;
	private String				clientId;
	private String				clientSecret;
	private String				redirectUri;

	/*
	 * 获得AccessTokenDto
	 */
	private AccessTokenDto loadAccessTokenDto(String fullUri, Map<String, String> params) {
		HttpClientExecutor executor = new HttpClientPostExecutor(fullUri);
		for (String key : params.keySet()) {
			executor.addRequestParam(key, params.get(key));
		}
		AccessTokenResponseHandler responseHandler = new AccessTokenResponseHandler();
		executor.execute(responseHandler);
		return responseHandler.getAccessTokenDto();
	}

	private AccessTokenDto loadAccessTokenDto(String fullUri) {
		HttpClientExecutor executor = new HttpClientPostExecutor(fullUri);
		AccessTokenResponseHandler responseHandler = new AccessTokenResponseHandler();
		executor.execute(responseHandler);
		return responseHandler.getAccessTokenDto();
	}

	/*
	 * 通过AuthAccessTokenDto数据获得AccessTokenDto
	 */
	@Override
	public AccessTokenDto retrieveAccessTokenDto(AuthAccessTokenDto tokenDto) {
		final String fullUri = tokenDto.getAccessTokenUri();
		LOG.debug("Get access_token URL: {}", fullUri);
		return loadAccessTokenDto(fullUri, tokenDto.getAuthCodeParams());
	}

	/*
	 * 封装请求accessToken数据
	 */
	@Override
	public AuthAccessTokenDto createAuthAccessTokenDto(AuthCallbackDto callbackDto) {
		return new AuthAccessTokenDto().setAccessTokenUri(accessTokenUri).setCode(callbackDto.getCode());
	}

	/*
	 * 根据accessToken获得UnityUserDto
	 */
	@Override
	public UserDto loadUnityUserDto(String accessToken, String infoUri) {
		LOG.debug("Load Unity-User_Info by access_token = {}", accessToken);
		if (StringUtils.isEmpty(accessToken)) {
			return new UserDto("Illegal 'access_token'", "'access_token' is empty");
		} else {
			HttpClientExecutor executor = new HttpClientExecutor(infoUri);
			executor.addRequestParam("access_token", accessToken);
			UserDtoResponseHandler responseHandler = new UserDtoResponseHandler();
			executor.execute(responseHandler);
			return responseHandler.getUserDto();
		}

	}

	@Override
	public AccessTokenDto retrievePasswordAccessTokenDto(AuthAccessTokenDto authAccessTokenDto) {
		final String fullUri = authAccessTokenDto.getAccessTokenUri();
		LOG.debug("Get [password] access_token URL: {}", fullUri);

		return loadAccessTokenDto(fullUri, authAccessTokenDto.getAccessTokenParams());
	}

	/*
	 * 刷新access_token
	 */
	@Override
	public AccessTokenDto refreshAccessTokenDto(RefreshAccessTokenDto refreshAccessTokenDto) {
		final String fullUri = refreshAccessTokenDto.getRefreshAccessTokenUri();
		LOG.debug("Get refresh_access_token URL: {}", fullUri);
		return loadAccessTokenDto(fullUri, refreshAccessTokenDto.getRefreshTokenParams());
	}

	/*
	 * 客户端模式授权业务处理
	 */
	@Override
	public Map<String, Object> retrieveCredentialsAccessTokenDto(AuthAccessTokenDto authAccessTokenDto) {
		Map<String, Object> result = new HashMap<String, Object>();
		final String uri = authAccessTokenDto.getAccessTokenUri();
		LOG.debug("Get [{}] access_token URL: {}", authAccessTokenDto.getGrantType(), uri);
		AccessTokenDto accessToken = loadAccessTokenDto(uri, authAccessTokenDto.getCredentialsParams());
		if (accessToken.error()) {
			result.put("flag", false);
			result.put("userDto", new UserDto(accessToken.getError(), accessToken.getErrorDescription()));
			return result;
		};
		result.put("flag", true);
		result.put("userDto", loadUnityUserDto(accessToken.getAccessToken(), unityUserInfoUri));
		return result;
	}

	/*
	 * 授权码模式，返回获得code跳转路径
	 */
	@Override
	public String submitAuthorizationCode(AuthorizationCodeDto codeDto) {
		request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		clientId = codeDto.getClientId();
		clientSecret = codeDto.getClientSecret();
		redirectUri = codeDto.getRedirectUri();
		// 保存state
		WebUtils.saveState(request, codeDto.getState());
		String fullUri = "";
		try {
			fullUri = codeDto.getFullUri();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		LOG.debug("Redirect to Oauth-Server URL: {}", fullUri);
		return "redirect:" + fullUri;
	}

	/*
	 * 授权模式获得授权后的资源信息
	 */
	@Override
	public Map<String, Object> authorizationCodeCallback(AuthCallbackDto callbackDto) {
		request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		Map<String, Object> result = new HashMap<String, Object>();
		// 判断返回code信息状态
		if (callbackDto.error()) {
			result.put("flag", false);
			result.put("userDto", new UserDto(callbackDto.getError(), callbackDto.getError_description()));
			return result;
		} else if (correctState(callbackDto, request)) {// 检验服务器返回的state和本地保存的是否一致
			// 获得从服务器获得 access_token
			AuthAccessTokenDto tokenDto = createAuthAccessTokenDto(callbackDto);
			tokenDto.setClientId(clientId);
			tokenDto.setClientSecret(clientSecret);
			tokenDto.setRedirectUri(redirectUri);
			final AccessTokenDto accessTokenDto = retrieveAccessTokenDto(tokenDto);
			if (accessTokenDto.error()) {
				result.put("flag", false);
				result.put("userDto", new UserDto(accessTokenDto.getError(), accessTokenDto.getErrorDescription()));
				return result;
			} else {
				// 获得授权的资源信息
				result.put("flag", true);
				result.put("userDto", loadUnityUserDto(accessTokenDto.getAccessToken(), unityUserInfoUri));
				return result;
			}
		} else {
			// illegal state
			result.put("flag", false);
			result.put("userDto", new UserDto("Invalid state", "Illegal \"state\": " + callbackDto.getState()));
			return result;
		}
	}

	// 检验服务器返回的state和本地保存的是否一致
	private boolean correctState(AuthCallbackDto callbackDto, HttpServletRequest request) {
		final String state = callbackDto.getState();
		return WebUtils.validateState(request, state);
	}

	/*
	 * 密码模式授权业务处理
	 */
	@Override
	public Map<String, Object> loadMobileUserDto(PasswordParams params) {
		Map<String, Object> result = new HashMap<String, Object>();
		params.setScope("read write");
		final String url = params.getFullUri().replace(" ", "%20");
		// 获得accessToken
		LOG.debug("Get access_token URL: {}", url);
		AccessTokenDto accessToken = loadAccessTokenDto(url);
		if (accessToken.error()) {
			result.put("flag", false);
			result.put("userDto", new UserDto(accessToken.getError(), accessToken.getErrorDescription()));
			return result;
		};
		LOG.debug("Get Mobile UserDto by uri={},accessToken={}", mobileUserInfoUri, accessToken.getAccessToken());
		// 获得授权用户信息
		result.put("flag", true);
		result.put("userDto", loadUnityUserDto(accessToken.getAccessToken(), mobileUserInfoUri));
		return result;
	}
}
